﻿/**!
 * AngularJS file upload/drop directive with http post and progress
 * @author  Danial  <danial.farid@gmail.com>
 * @version <%= pkg.version %>
 */
(function () {

    var angularFileUpload = angular.module('angularFileUpload', []);

    angularFileUpload.service('$upload', ['$http', '$q', '$timeout', function ($http, $q, $timeout) {
        function sendHttp(config) {
            config.method = config.method || 'POST';
            config.headers = config.headers || {};
            config.transformRequest = config.transformRequest || function (data, headersGetter) {
                if (window.ArrayBuffer && data instanceof window.ArrayBuffer) {
                    return data;
                }
                return $http.defaults.transformRequest[0](data, headersGetter);
            };
            var deferred = $q.defer();

            if (window.XMLHttpRequest.__isShim) {
                config.headers['__setXHR_'] = function () {
                    return function (xhr) {
                        if (!xhr) return;
                        config.__XHR = xhr;
                        config.xhrFn && config.xhrFn(xhr);
                        xhr.upload.addEventListener('progress', function (e) {
                            deferred.notify(e);
                        }, false);
                        //fix for firefox not firing upload progress end, also IE8-9
                        xhr.upload.addEventListener('load', function (e) {
                            if (e.lengthComputable) {
                                deferred.notify(e);
                            }
                        }, false);
                    };
                };
            }

            $http(config).then(function (r) { deferred.resolve(r) }, function (e) { deferred.reject(e) }, function (n) { deferred.notify(n) });

            var promise = deferred.promise;
            promise.success = function (fn) {
                promise.then(function (response) {
                    fn(response.data, response.status, response.headers, config);
                });
                return promise;
            };

            promise.error = function (fn) {
                promise.then(null, function (response) {
                    fn(response.data, response.status, response.headers, config);
                });
                return promise;
            };

            promise.progress = function (fn) {
                promise.then(null, null, function (update) {
                    fn(update);
                });
                return promise;
            };
            promise.abort = function () {
                if (config.__XHR) {
                    $timeout(function () {
                        config.__XHR.abort();
                    });
                }
                return promise;
            };
            promise.xhr = function (fn) {
                config.xhrFn = (function (origXhrFn) {
                    return function () {
                        origXhrFn && origXhrFn.apply(promise, arguments);
                        fn.apply(promise, arguments);
                    }
                })(config.xhrFn);
                return promise;
            };

            return promise;
        }

        this.upload = function (config) {
            config.headers = config.headers || {};
            config.headers['Content-Type'] = undefined;
            config.transformRequest = config.transformRequest || $http.defaults.transformRequest;
            var formData = new FormData();
            var origTransformRequest = config.transformRequest;
            var origData = config.data;
            config.transformRequest = function (formData, headerGetter) {
                if (origData) {
                    if (config.formDataAppender) {
                        for (var key in origData) {
                            var val = origData[key];
                            config.formDataAppender(formData, key, val);
                        }
                    } else {
                        for (var key in origData) {
                            var val = origData[key];
                            if (typeof origTransformRequest == 'function') {
                                val = origTransformRequest(val, headerGetter);
                            } else {
                                for (var i = 0; i < origTransformRequest.length; i++) {
                                    var transformFn = origTransformRequest[i];
                                    if (typeof transformFn == 'function') {
                                        val = transformFn(val, headerGetter);
                                    }
                                }
                            }
                            formData.append(key, val);
                        }
                    }
                }

                if (config.file != null) {
                    var fileFormName = config.fileFormDataName || 'file';

                    if (Object.prototype.toString.call(config.file) === '[object Array]') {
                        var isFileFormNameString = Object.prototype.toString.call(fileFormName) === '[object String]';
                        for (var i = 0; i < config.file.length; i++) {
                            formData.append(isFileFormNameString ? fileFormName : fileFormName[i], config.file[i],
                                    (config.fileName && config.fileName[i]) || config.file[i].name);
                        }
                    } else {
                        formData.append(fileFormName, config.file, config.fileName || config.file.name);
                    }
                }
                return formData;
            };

            config.data = formData;

            return sendHttp(config);
        };

        this.http = function (config) {
            return sendHttp(config);
        }
    }]);

    angularFileUpload.directive('ngFileSelect', ['$parse', '$timeout', function ($parse, $timeout) {
        return function (scope, elem, attr) {
            var fn = $parse(attr['ngFileSelect']);
            elem.bind('change', function (evt) {
                var files = [], fileList, i;
                fileList = evt.target.files;
                if (fileList != null) {
                    for (i = 0; i < fileList.length; i++) {
                        files.push(fileList.item(i));
                    }
                }
                $timeout(function () {
                    fn(scope, {
                        $files: files,
                        $event: evt
                    });
                });
            });
            // removed this since it was confusing if the user click on browse and then cancel #181
            //		elem.bind('click', function(){
            //			this.value = null;
            //		});

            // touch screens
            if (('ontouchstart' in window) ||
                    (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) {
                elem.bind('touchend', function (e) {
                    e.preventDefault();
                    e.target.click();
                });
            }
        };
    }]);

    angularFileUpload.directive('ngFileDropAvailable', ['$parse', '$timeout', function ($parse, $timeout) {
        return function (scope, elem, attr) {
            if ('draggable' in document.createElement('span')) {
                var fn = $parse(attr['ngFileDropAvailable']);
                $timeout(function () {
                    fn(scope);
                });
            }
        };
    }]);

    angularFileUpload.directive('ngFileDrop', ['$parse', '$timeout', '$location', function ($parse, $timeout, $location) {
        return function (scope, elem, attr) {
            if ('draggable' in document.createElement('span')) {
                var leaveTimeout = null;
                elem[0].addEventListener("dragover", function (evt) {
                    evt.stopPropagation();
                    evt.preventDefault();
                    elem.addClass(elem[0].__drag_over_class_);
                    $timeout.cancel(leaveTimeout);
                    if (!elem[0].__drag_entered_) {
                        elem[0].__drag_entered_ = true;
                        var dragOverClassFn = $parse(attr['ngFileDragOverClass']);
                        if (dragOverClassFn instanceof Function) {
                            var dragOverClass = dragOverClassFn(scope, {
                                $event: evt
                            });
                            elem[0].__drag_over_class_ = dragOverClass;
                            elem.addClass(elem[0].__drag_over_class_);
                        } else {
                            elem[0].__drag_over_class_ = attr['ngFileDragOverClass'] || "dragover";
                            elem.addClass(elem[0].__drag_over_class_);
                        }
                    }
                }, false);
                elem[0].addEventListener("dragenter", function (evt) {
                    evt.stopPropagation();
                    evt.preventDefault();
                }, false);
                elem[0].addEventListener("dragleave", function (evt) {
                    leaveTimeout = $timeout(function () {
                        elem[0].__drag_entered_ = false;
                        elem.removeClass(elem[0].__drag_over_class_);
                    });
                }, false);
                var fn = $parse(attr['ngFileDrop']);
                elem[0].addEventListener("drop", function (evt) {
                    evt.stopPropagation();
                    evt.preventDefault();
                    elem[0].__drag_entered_ = false;
                    elem.removeClass(elem[0].__drag_over_class_);
                    extractFiles(evt, function (files) {
                        fn(scope, {
                            $files: files,
                            $event: evt
                        });
                    });
                }, false);

                function isASCII(str) {
                    return /^[\000-\177]*$/.test(str);
                }

                function extractFiles(evt, callback) {
                    var files = [], items = evt.dataTransfer.items;
                    if (items && items.length > 0 && items[0].webkitGetAsEntry && $location.protocol() != 'file') {
                        for (var i = 0; i < items.length; i++) {
                            var entry = items[i].webkitGetAsEntry();
                            if (entry != null) {
                                //fix for chrome bug https://code.google.com/p/chromium/issues/detail?id=149735
                                if (isASCII(entry.name)) {
                                    traverseFileTree(files, entry);
                                } else {
                                    files.push(items[i].getAsFile());
                                }
                            }
                        }
                    } else {
                        var fileList = evt.dataTransfer.files;
                        if (fileList != null) {
                            for (var i = 0; i < fileList.length; i++) {
                                files.push(fileList.item(i));
                            }
                        }
                    }
                    (function waitForProcess(delay) {
                        $timeout(function () {
                            if (!processing) {
                                callback(files);
                            } else {
                                waitForProcess(10);
                            }
                        }, delay || 0)
                    })();
                }

                var processing = 0;
                function traverseFileTree(files, entry) {
                    if (entry != null) {
                        if (entry.isDirectory) {
                            var dirReader = entry.createReader();
                            processing++;
                            dirReader.readEntries(function (entries) {
                                for (var i = 0; i < entries.length; i++) {
                                    traverseFileTree(files, entries[i]);
                                }
                                processing--;
                            });
                        } else {
                            processing++;
                            entry.file(function (file) {
                                processing--;
                                files.push(file);
                            });
                        }
                    }
                }
            }
        };
    }]);

})();